#include <QDataStream>

#include <algorithm>
#include <cmath>

#include "FrakOutConstants.h"
#include "FrakOutCurrentVersion.h"
#include "FrakOutData.h"
#include "migration/FrakoutMigrationController.h"
#include "FrakOutUnknownFileTypeException.h"
#include "FrakOutVersion.h"

FrakOutData::FrakOutData() : m_cellScaleFactor(2.0)
{
}

FrakOutData::FrakOutData(const QPixmap& image) : m_cellScaleFactor(2.0)
{
    m_image = image;
}

FrakOutData::FrakOutData(const QPixmap& image, const QVector<FrakOutDataItem>& items, double cellScaleFactor) :
        m_cellScaleFactor(cellScaleFactor), m_image(image), m_items(items)
{
}

void FrakOutData::addCell(int pos, const QPair<int,int>& cell)
{
    if (pos >= 0 && pos < m_items.size())
    {
        FrakOutDataItem *data = m_items.data();

        data[pos].addCell(cell);
    }
}

void FrakOutData::addItem(const FrakOutDataItem& item)
{
    m_items.push_back(item);
}

void FrakOutData::addItem(int pos, const FrakOutDataItem& item)
{
    m_items.insert(pos, item);
}

bool FrakOutData::cellExists(int pos, const QPair<int,int>& cell) const
{
    return m_items.at(pos).cellExists(cell);
}

double FrakOutData::cellScaleFactor() const
{
    return m_cellScaleFactor;
}

void FrakOutData::clearSheets()
{
    m_items.clear();
}

const QPixmap& FrakOutData::image() const
{
    return m_image;
}

void FrakOutData::init(const QString& filename)
{
    QPixmap pixmap;

    pixmap.load(filename);
    m_items.clear();
    m_image = pixmap;
}

double FrakOutData::initialCellSize() const
{
    double cellSize = 30;

    if (!m_image.isNull())
    {
        cellSize = double(std::max(m_image.width(), m_image.height()));
    }

    return cellSize;
}

const QVector<FrakOutDataItem>& FrakOutData::items() const
{
    return m_items;
}

int FrakOutData::numItems() const
{
    return m_items.size();
}

void FrakOutData::removeCell(int pos, const QPair<int,int>& cell)
{
    if (pos >= 0 && pos < m_items.size())
    {
        FrakOutDataItem *data = m_items.data();

        data[pos].removeCell(cell);
    }
}

void FrakOutData::removeCells(int pos)
{
    if (pos >= 0 && pos < m_items.size())
    {
        FrakOutDataItem *data = m_items.data();

        data[pos].clearCells();
    }
}

void FrakOutData::removeItem(int i)
{
    m_items.remove(i);
}

void FrakOutData::setCells(int pos, const QSet<QPair<int,int> >& cells)
{
    if (pos >= 0 && pos < m_items.size())
    {
        FrakOutDataItem *data = m_items.data();
        data[pos].setCells(cells);
    }
}

void FrakOutData::setCellScaleFactor(double aCellScalefactor)
{
    m_cellScaleFactor = aCellScalefactor;
}

#ifdef CJR_OLD
QDataStream& operator<<(QDataStream& out, const FrakOutData& fracalcData)
{
    out.setVersion(FRACALC_QT_VERSION_1_0_0);
    out << quint32(FRACALC_MAGIC_NUMBER) << quint16(FRACALC_VERSION_1_0_0);
    out << fracalcData.image() << fracalcData.items() << fracalcData.cellScaleFactor();
    return out;
}
#else
QDataStream& operator<<(QDataStream& out, const FrakOutData& frakOutData)
{
    const FrakOutVersion* const currentVersion = FrakOutCurrentVersion::instance()->version();

    out.setVersion(currentVersion->qtVersion());
    out << quint32(FRACALC_MAGIC_NUMBER) << quint16(currentVersion->versionNumber());
    out << frakOutData.image() << frakOutData.items() << frakOutData.cellScaleFactor();
    return out;
}
#endif

QDataStream& operator>>(QDataStream &in, FrakOutData& frakOutData)
{
    QVector<FrakOutDataItem> items;
    QPixmap image;
    double cellScaleFactor;
    quint32 magicNumber;
    quint16 versionNumber;
    unsigned int currentVersionNumber = FrakOutCurrentVersion::instance()->version()->versionNumber();
    in >> magicNumber >> versionNumber;
    if (magicNumber != FRACALC_MAGIC_NUMBER)
        throw FrakOutUnknownFileTypeException("Unknown file");

    if (versionNumber < currentVersionNumber)
        throw FrakOutUnknownFileTypeException("Version number is greater than current version number.");

    in >> image >> items >> cellScaleFactor;
    frakOutData = FrakOutData(image, items, cellScaleFactor);

    if (versionNumber > currentVersionNumber)
    {
        FrakOutMigrationController migrationController;

        migrationController.migrate(frakOutData,
                                    FrakOutVersion::version(versionNumber),
                                    FrakOutVersion::version(currentVersionNumber));
    }

    return in;
}
